deploy: 휴면 스케줄러, GitHub OAuth, 예외 처리 리팩토링, 테스트 코드 dev-deploy 반영 - #35
deploy: 휴면 스케줄러, GitHub OAuth, 예외 처리 리팩토링, 테스트 코드 dev-deploy 반영#35Jungeunsun565 wants to merge 22 commits into
Conversation
feat: GitHub OAuth 로그인 추가 및 트랜잭션 리팩토링 - GitHub/Google OAuth 로그인 구현 - 트랜잭션 처리 리팩토링 - 불필요한 이메일 주소 제거 - FeignClient 어노테이션 수정
refactor: 전역 예외 처리 및 응답 구조 리팩토링 fix: Feign Circuit Breaker ID의 하이픈 제거 방지 설정 추가
…r-filter refactor: HeaderAuthenticationFilter X-User-Role 헤더 사용
test: User, UserCredential, UserRole, Oauth 엔티티 테스트 작성
…authRepository 테스트 작성
test: Repository 계층 테스트 작성
|
Warning Review limit reached
Next review available in: 4 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (10)
📝 WalkthroughWalkthrough인증 서비스에 Google·GitHub OAuth, Core 서비스 연동, 직접 응답 반환, 전역 예외 처리, 헤더 기반 권한 인증, Redis 분산 잠금 스케줄러를 추가했습니다. 엔터티와 저장소 테스트도 추가했습니다. Changes인증 및 사용자 수명주기
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant UserController
participant UserServiceImpl
participant OauthClientResolver
participant GithubOauthClient
participant CoreClient
UserController->>UserServiceImpl: OAuth provider와 code 전달
UserServiceImpl->>OauthClientResolver: provider별 클라이언트 조회
OauthClientResolver->>GithubOauthClient: code로 사용자 정보 조회
GithubOauthClient-->>UserServiceImpl: OauthUserInfo 반환
UserController->>UserServiceImpl: 회원 탈퇴 요청
UserServiceImpl->>CoreClient: 관리자 그룹 존재 여부 조회
CoreClient-->>UserServiceImpl: ManagerGroupExistsResponse 반환
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/nhnacademy/insightonauth/client/impl/GithubOauthClient.java`:
- Around line 90-94: Update GithubOauthClient’s email selection to require both
primary and verified to be true, rejecting authentication or linking when no
such email exists. In
src/main/java/com/nhnacademy/insightonauth/client/impl/GithubOauthClient.java
lines 90-94, apply this filter before selecting the email. In
src/main/java/com/nhnacademy/insightonauth/client/impl/GoogleOauthClient.java
lines 70-74, validate email_verified is true before constructing OauthUserInfo
and reject the request otherwise.
- Line 30: Update the RestClient initialization in GithubOauthClient to
configure a 3-second connection timeout and a 5-second read timeout, matching
the timeout setup used by GoogleOauthClient for GitHub token, user, and email
requests.
In `@src/main/java/com/nhnacademy/insightonauth/exception/ErrorResponse.java`:
- Around line 3-6: ErrorResponse에 외부 응답용 안정적인 ErrorCode 필드를 추가하고,
src/main/java/com/nhnacademy/insightonauth/exception/ErrorResponse.java:3-6의
생성자·직렬화 구조를 이에 맞게 갱신하세요.
src/main/java/com/nhnacademy/insightonauth/handler/GlobalExceptionHandler.java:19-21의
handleBusinessException()에서 BusinessException.getErrorCode()를 새 응답 필드로 매핑해
클라이언트가 오류 코드를 받을 수 있도록 수정하세요.
In `@src/main/java/com/nhnacademy/insightonauth/redis/RedisService.java`:
- Around line 32-34: RedisService.java:32-34의 RedisService.setIfAbsent와 락 해제
API를 확장해 락 획득 시 고유 토큰을 저장하고, 동일 토큰일 때만 원자적으로 삭제하는 compare-and-delete를 구현하세요.
UserHardDeleteScheduler.java:25-46의 UserHardDeleteScheduler와
UserSleepConversionScheduler.java:25-48의 UserSleepConversionScheduler에서는
"locked" 대신 인스턴스별 고유 토큰을 사용하고, finally에서 해당 토큰을 전달해 조건부 해제 API를 호출하세요.
In
`@src/main/java/com/nhnacademy/insightonauth/scheduler/UserSleepConversionScheduler.java`:
- Line 23: Update the `@Scheduled` declaration in UserSleepConversionScheduler so
the cron expression and its Korean time comment describe the same execution
time; either change the cron to run at 04:00 or revise the comment to 02:00,
preserving the intended schedule.
In
`@src/main/java/com/nhnacademy/insightonauth/service/impl/UserServiceImpl.java`:
- Around line 334-335: OAuth 로그인 흐름에서 OauthClient.getUserInfo 이후 토큰을 발급하기 전에
신규·기존 사용자 모두의 lastLoginAt을 현재 시각으로 갱신하도록 UserServiceImpl의 관련 분기 로직을 수정하세요. 사용자
저장까지 완료한 뒤 토큰 발급이 진행되도록 보장하고, 두 OAuth 로그인 경로의 lastLoginAt 갱신을 검증하는 테스트를 추가하세요.
In `@src/main/resources/application-dev.properties`:
- Around line 92-100: Update the Resilience4j circuit-breaker instance keys in
src/main/resources/application-dev.properties:92-100 and
src/test/resources/application.properties:91-95 to match the actual method-based
Feign IDs generated for CoreClient, such as the ID for existsManagerGroup(String
userId), while preserving the shared alphanumeric-ids.enabled=false policy in
both profiles. Replace the ineffective insighton-core key consistently at both
sites so the sliding-window, failure-rate, and wait-duration settings are
applied.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7a3b7453-95cb-4b30-a4eb-22a370a65e85
📒 Files selected for processing (66)
pom.xmlsrc/main/java/com/nhnacademy/insightonauth/InsightonAuthApplication.javasrc/main/java/com/nhnacademy/insightonauth/client/CoreClient.javasrc/main/java/com/nhnacademy/insightonauth/client/OauthClient.javasrc/main/java/com/nhnacademy/insightonauth/client/OauthClientResolver.javasrc/main/java/com/nhnacademy/insightonauth/client/impl/GithubOauthClient.javasrc/main/java/com/nhnacademy/insightonauth/client/impl/GoogleOauthClient.javasrc/main/java/com/nhnacademy/insightonauth/controller/AdminController.javasrc/main/java/com/nhnacademy/insightonauth/controller/CoreController.javasrc/main/java/com/nhnacademy/insightonauth/controller/MypageController.javasrc/main/java/com/nhnacademy/insightonauth/controller/UserController.javasrc/main/java/com/nhnacademy/insightonauth/dto/ApiResponse.javasrc/main/java/com/nhnacademy/insightonauth/dto/ErrorResponse.javasrc/main/java/com/nhnacademy/insightonauth/dto/core/AuthUserResponse.javasrc/main/java/com/nhnacademy/insightonauth/dto/core/ManagerGroupExistsResponse.javasrc/main/java/com/nhnacademy/insightonauth/email/EmailService.javasrc/main/java/com/nhnacademy/insightonauth/exception/BusinessException.javasrc/main/java/com/nhnacademy/insightonauth/exception/CoreServiceUnavailableException.javasrc/main/java/com/nhnacademy/insightonauth/exception/DuplicateEmailException.javasrc/main/java/com/nhnacademy/insightonauth/exception/DuplicatePhoneNumberException.javasrc/main/java/com/nhnacademy/insightonauth/exception/EmailNotFoundException.javasrc/main/java/com/nhnacademy/insightonauth/exception/EmailSendException.javasrc/main/java/com/nhnacademy/insightonauth/exception/ErrorCode.javasrc/main/java/com/nhnacademy/insightonauth/exception/ErrorResponse.javasrc/main/java/com/nhnacademy/insightonauth/exception/InvalidCredentialsException.javasrc/main/java/com/nhnacademy/insightonauth/exception/InvalidReactiveTokenException.javasrc/main/java/com/nhnacademy/insightonauth/exception/InvalidRefreshTokenException.javasrc/main/java/com/nhnacademy/insightonauth/exception/InvalidUserException.javasrc/main/java/com/nhnacademy/insightonauth/exception/InvalidUserStatusException.javasrc/main/java/com/nhnacademy/insightonauth/exception/InvalidVerificationCodeException.javasrc/main/java/com/nhnacademy/insightonauth/exception/InvalidVerificationTokenException.javasrc/main/java/com/nhnacademy/insightonauth/exception/LastLoginMethodException.javasrc/main/java/com/nhnacademy/insightonauth/exception/LoginTemporarilyLockedException.javasrc/main/java/com/nhnacademy/insightonauth/exception/ManagerGroupExistsException.javasrc/main/java/com/nhnacademy/insightonauth/exception/OauthAlreadyLinkedException.javasrc/main/java/com/nhnacademy/insightonauth/exception/OauthNotFoundException.javasrc/main/java/com/nhnacademy/insightonauth/exception/RefreshTokenNotFoundException.javasrc/main/java/com/nhnacademy/insightonauth/exception/RestorePeriodExpiredException.javasrc/main/java/com/nhnacademy/insightonauth/exception/UnsupportedOAuthProviderException.javasrc/main/java/com/nhnacademy/insightonauth/exception/UserCredentialsNotFoundException.javasrc/main/java/com/nhnacademy/insightonauth/exception/UserNotFoundException.javasrc/main/java/com/nhnacademy/insightonauth/exception/UserRoleNotFoundException.javasrc/main/java/com/nhnacademy/insightonauth/exception/VerificationTemporarilyLockedException.javasrc/main/java/com/nhnacademy/insightonauth/filter/HeaderAuthenticationFilter.javasrc/main/java/com/nhnacademy/insightonauth/handler/GlobalExceptionHandler.javasrc/main/java/com/nhnacademy/insightonauth/redis/RedisKey.javasrc/main/java/com/nhnacademy/insightonauth/redis/RedisService.javasrc/main/java/com/nhnacademy/insightonauth/repository/UserRepository.javasrc/main/java/com/nhnacademy/insightonauth/scheduler/UserHardDeleteScheduler.javasrc/main/java/com/nhnacademy/insightonauth/scheduler/UserSleepConversionScheduler.javasrc/main/java/com/nhnacademy/insightonauth/service/UserService.javasrc/main/java/com/nhnacademy/insightonauth/service/impl/MyPageServiceImpl.javasrc/main/java/com/nhnacademy/insightonauth/service/impl/OauthServiceImpl.javasrc/main/java/com/nhnacademy/insightonauth/service/impl/UserCredentialServiceImpl.javasrc/main/java/com/nhnacademy/insightonauth/service/impl/UserRoleServiceImpl.javasrc/main/java/com/nhnacademy/insightonauth/service/impl/UserServiceImpl.javasrc/main/resources/application-dev.propertiessrc/test/java/com/nhnacademy/insightonauth/entity/OauthTest.javasrc/test/java/com/nhnacademy/insightonauth/entity/UserCredentialTest.javasrc/test/java/com/nhnacademy/insightonauth/entity/UserRoleTest.javasrc/test/java/com/nhnacademy/insightonauth/entity/UserTest.javasrc/test/java/com/nhnacademy/insightonauth/repository/OauthRepositoryTest.javasrc/test/java/com/nhnacademy/insightonauth/repository/UserCredentialRepositoryTest.javasrc/test/java/com/nhnacademy/insightonauth/repository/UserRepositoryTest.javasrc/test/java/com/nhnacademy/insightonauth/repository/UserRoleRepositoryTest.javasrc/test/resources/application.properties
💤 Files with no reviewable changes (3)
- src/main/java/com/nhnacademy/insightonauth/dto/ApiResponse.java
- src/main/java/com/nhnacademy/insightonauth/dto/ErrorResponse.java
- src/main/java/com/nhnacademy/insightonauth/email/EmailService.java
| public record ErrorResponse( | ||
| int status, | ||
| String message | ||
| ) { |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)ErrorResponse\.java$|GlobalExceptionHandler\.java$|BusinessException\.java$|ErrorCode\.java$|.*\.md$|.*\.adoc$|.*\.json$|.*\.yml$|.*\.yaml$' || true
echo "== ErrorResponse =="
if [ -f src/main/java/com/nhnacademy/insightonauth/exception/ErrorResponse.java ]; then
cat -n src/main/java/com/nhnacademy/insightonauth/exception/ErrorResponse.java
fi
echo "== GlobalExceptionHandler =="
if [ -f src/main/java/com/nhnacademy/insightonauth/handler/GlobalExceptionHandler.java ]; then
cat -n src/main/java/com/nhnacademy/insightonauth/handler/GlobalExceptionHandler.java
fi
echo "== search responses and error code contracts =="
rg -n "ErrorResponse|errorCode|errorCodeValue|ErrorCode|BusinessException|error\\b|code|message" -S src README* docs .github 2>/dev/null || trueRepository: nhnacademy-aiot3-insighton/InsightOn-auth
Length of output: 33915
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== BusinessException =="
cat -n src/main/java/com/nhnacademy/insightonauth/exception/BusinessException.java
echo "== ErrorCode =="
cat -n src/main/java/com/nhnacademy/insightonauth/exception/ErrorCode.java
echo "== Java record field order/record method resolution behavior probe =="
cat > /tmp/RecordBehaviorProbe.java <<'JAVA'
enum TestStatus {
OK("OK");
private final String status;
TestStatus(String status) { this.status = status; }
public String status() { return status; }
}
record ErrorResponse(int status, String message) {
public String status() {
return "OVERRIDDEN";
}
}
public class RecordBehaviorProbe {
public static void main(String[] args) {
ErrorResponse r = new ErrorResponse(400, "msg");
System.out.println("status field: " + r.status());
System.out.println("status: " + r.status());
}
}
JAVA
javac /tmp/RecordBehaviorProbe.java 2>&1 || true
if [ -f /tmp/RecordBehaviorProbe.class ]; then
java -cp /tmp RecordBehaviorProbe
fi
echo "== record field / method order consistency in ErrorResponse =="
python3 - <<'PY'
from pathlib import Path
s = Path('src/main/java/com/nhnacademy/insightonauth/exception/ErrorResponse.java').read_text()
print(s)
print("contains ErrorCode parameter:", "ErrorCode" in s)
print("has overriding status() in ErrorResponse:", "ErrorResponse" in s and "public String status()" in s)
PYRepository: nhnacademy-aiot3-insighton/InsightOn-auth
Length of output: 3003
오류 응답에 ErrorCode를 포함하세요. ErrorResponse가 status 필드만 유지하고, handleBusinessException()도 getErrorCode()를 직렬화하지 않아 클라이언트가 BusinessException의 안정적인 오류 코드를 처리할 수 없습니다. 외부 응답에 안정적인 코드 필드를 추가하고 비즈니스 예외가 이를 매핑하도록 수정하세요.
📍 Affects 2 files
src/main/java/com/nhnacademy/insightonauth/exception/ErrorResponse.java#L3-L6(this comment)src/main/java/com/nhnacademy/insightonauth/handler/GlobalExceptionHandler.java#L19-L21
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/java/com/nhnacademy/insightonauth/exception/ErrorResponse.java`
around lines 3 - 6, ErrorResponse에 외부 응답용 안정적인 ErrorCode 필드를 추가하고,
src/main/java/com/nhnacademy/insightonauth/exception/ErrorResponse.java:3-6의
생성자·직렬화 구조를 이에 맞게 갱신하세요.
src/main/java/com/nhnacademy/insightonauth/handler/GlobalExceptionHandler.java:19-21의
handleBusinessException()에서 BusinessException.getErrorCode()를 새 응답 필드로 매핑해
클라이언트가 오류 코드를 받을 수 있도록 수정하세요.
There was a problem hiding this comment.
팀 논의가 필요한 사안이라 이번 PR에서는 보류하고, 별도 이슈로 이어가겠습니다.
There was a problem hiding this comment.
@Jungeunsun565, 확인했습니다. 이 변경은 현재 PR 범위에서 보류하겠습니다.
후속 GitHub 이슈를 생성할까요?
You are interacting with an AI system.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/main/java/com/nhnacademy/insightonauth/scheduler/UserHardDeleteScheduler.java (2)
47-55: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win삭제 성공 건수와 대상 건수를 분리하세요.
Line 50에서 개별 삭제 실패를 처리하지만 Line 55는 실패한 대상까지 포함한
targets.size()를 “완료” 건수로 기록합니다. 부분 실패가 전체 성공으로 보이므로 운영자가 재처리 필요성을 놓칠 수 있습니다. 성공 건수와 실패 건수 또는 실패한userId목록을 별도로 기록하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/nhnacademy/insightonauth/scheduler/UserHardDeleteScheduler.java` around lines 47 - 55, Update the deletion loop in UserHardDeleteScheduler to track successful and failed deletions separately: increment a success counter only after userService.deleteUser completes, and record failures in the existing catch block. Change the final log so it reports the actual success count separately from the total targets, with failure information or failed userIds included.
44-50: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win삭제 직전에 대상 조건을 원자적으로 다시 확인하세요.
Line 45에서 만료 탈퇴 사용자 목록을 조회한 뒤 Line 49에서 ID만
deleteUser()에 전달합니다.deleteUser()는 ID로 사용자를 다시 조회하지만Status.WITHDRAW와 만료 시점을 검증하지 않습니다. 조회와 삭제 사이에 상태가 변경되면 더 이상 삭제 대상이 아닌 사용자를 물리 삭제할 수 있습니다. 전용 hard-delete 메서드에서 상태·withdrawnAt조건을 같은 트랜잭션으로 재검증하거나 조건부 삭제를 사용하세요.PR 목표의 “탈퇴 만료 사용자 삭제” 기능을 기준으로 확인했습니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/nhnacademy/insightonauth/scheduler/UserHardDeleteScheduler.java` around lines 44 - 50, Update the hard-delete flow in UserHardDeleteScheduler to revalidate each user’s Status.WITHDRAW and expired withdrawnAt condition atomically immediately before deletion. Use or introduce a dedicated hard-delete operation in userService that performs the conditional check and physical delete within one transaction, replacing the unconditional deleteUser(user.getUserId()) call while preserving per-user exception handling.src/main/java/com/nhnacademy/insightonauth/entity/User.java (1)
43-44: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win테스트의
setLastLoginAt(...)호출을 변경하세요.
UserRepositoryTest.java의 두 호출은 제거된 setter를 참조하므로 컴파일이 실패합니다. 과거 시각을 설정할 수 있는 테스트 경로를 제공하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/nhnacademy/insightonauth/entity/User.java` around lines 43 - 44, UserRepositoryTest.java에서 제거된 setLastLoginAt(...) 두 호출을 지원되는 초기화 경로로 변경하고, 과거 시각의 lastLoginAt 값을 설정할 수 있도록 User 엔티티의 생성자·팩토리·테스트용 설정 경로를 제공하세요. 일반 사용 흐름과 lastLoginAt 필드의 저장 동작은 유지해야 합니다.src/main/java/com/nhnacademy/insightonauth/service/impl/UserServiceImpl.java (1)
271-278: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftCore 호출과 탈퇴 처리를 분리하세요.
UserServiceImpl의 class-level@Transactional로withdraw()의 데이터베이스 트랜잭션이coreClient.existsManagerGroup()의 응답까지 유지됩니다. 현재 설정에는 Feign connect/read timeout과TimeLimiter가 없으며, 회로 차단기 설정만으로 대기 시간을 제한할 수 없습니다. 원격 검증을 non-transactional 경계로 분리하고, 탈퇴 변경은 짧은 별도 트랜잭션에서 처리하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/nhnacademy/insightonauth/service/impl/UserServiceImpl.java` around lines 271 - 278, UserServiceImpl.withdraw() currently holds its database transaction while waiting for coreClient.existsManagerGroup(). Move the Core manager-group validation into a non-transactional method or collaborator, then invoke a separate transactional method for the withdrawal database changes after validation succeeds. Ensure the remote call is completed before the short transaction begins.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/nhnacademy/insightonauth/scheduler/UserHardDeleteScheduler.java`:
- Around line 57-60: Update the unlock handling in UserHardDeleteScheduler so an
IllegalMonitorStateException from lock.unlock() is caught when ownership is lost
between isHeldByCurrentThread() and unlock(). Log the ownership-loss event at
warning level, and prevent the exception from propagating through the finally
block.
In
`@src/main/java/com/nhnacademy/insightonauth/service/impl/UserServiceImpl.java`:
- Around line 352-355: oauthLogin에서 새 User를 저장하기 전에 userRepository로
userInfo.email()의 기존 계정을 조회하도록 수정하세요. 기존 일반 계정 또는 다른 OAuth 계정이 있으면 검증된 이메일과 명시적
연결 정책을 확인한 경우에만 해당 계정에 OAuth 정보를 연결하고, 검증되지 않은 이메일만으로는 자동 연결하지 말고 명확히 거부하세요. 기존
계정이 없을 때만 현재의 User 생성, MEMBER 역할 부여, OAuth 생성 흐름을 수행하세요.
- Around line 352-353: UserServiceImpl의 사용자 생성 흐름에서 userInfo.email()을 직접 사용하지
말고, GitHub OAuth authorization에 user:email scope를 추가한 뒤 모든 경로에서 /user/emails 조회
결과 중 verified이면서 primary인 이메일을 사용하세요. 적합한 이메일이 없으면 User 생성 또는
userRepository.save(newUser) 전에 EmailNotFoundException을 반환하도록 수정하세요.
---
Outside diff comments:
In `@src/main/java/com/nhnacademy/insightonauth/entity/User.java`:
- Around line 43-44: UserRepositoryTest.java에서 제거된 setLastLoginAt(...) 두 호출을
지원되는 초기화 경로로 변경하고, 과거 시각의 lastLoginAt 값을 설정할 수 있도록 User 엔티티의 생성자·팩토리·테스트용 설정 경로를
제공하세요. 일반 사용 흐름과 lastLoginAt 필드의 저장 동작은 유지해야 합니다.
In
`@src/main/java/com/nhnacademy/insightonauth/scheduler/UserHardDeleteScheduler.java`:
- Around line 47-55: Update the deletion loop in UserHardDeleteScheduler to
track successful and failed deletions separately: increment a success counter
only after userService.deleteUser completes, and record failures in the existing
catch block. Change the final log so it reports the actual success count
separately from the total targets, with failure information or failed userIds
included.
- Around line 44-50: Update the hard-delete flow in UserHardDeleteScheduler to
revalidate each user’s Status.WITHDRAW and expired withdrawnAt condition
atomically immediately before deletion. Use or introduce a dedicated hard-delete
operation in userService that performs the conditional check and physical delete
within one transaction, replacing the unconditional deleteUser(user.getUserId())
call while preserving per-user exception handling.
In
`@src/main/java/com/nhnacademy/insightonauth/service/impl/UserServiceImpl.java`:
- Around line 271-278: UserServiceImpl.withdraw() currently holds its database
transaction while waiting for coreClient.existsManagerGroup(). Move the Core
manager-group validation into a non-transactional method or collaborator, then
invoke a separate transactional method for the withdrawal database changes after
validation succeeds. Ensure the remote call is completed before the short
transaction begins.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 8d304952-1dd7-48fd-9094-5e89f1b83a5d
📒 Files selected for processing (8)
pom.xmlsrc/main/java/com/nhnacademy/insightonauth/client/impl/GithubOauthClient.javasrc/main/java/com/nhnacademy/insightonauth/entity/User.javasrc/main/java/com/nhnacademy/insightonauth/scheduler/UserHardDeleteScheduler.javasrc/main/java/com/nhnacademy/insightonauth/scheduler/UserSleepConversionScheduler.javasrc/main/java/com/nhnacademy/insightonauth/service/UserService.javasrc/main/java/com/nhnacademy/insightonauth/service/impl/UserServiceImpl.javasrc/main/resources/application-dev.properties
💤 Files with no reviewable changes (1)
- src/main/java/com/nhnacademy/insightonauth/service/UserService.java
🚧 Files skipped from review as they are similar to previous changes (3)
- src/main/resources/application-dev.properties
- src/main/java/com/nhnacademy/insightonauth/client/impl/GithubOauthClient.java
- src/main/java/com/nhnacademy/insightonauth/scheduler/UserSleepConversionScheduler.java
🔀 PR 개요
dev 브랜치에 누적된 아래 기능/개선 사항을 dev-deploy에 반영합니다.
📄 변경 사항
어떤 부분이 수정/추가/삭제되었는지 구체적으로 기술해 주세요.
💡 상세 내용
Feature
Fix
Refactor
Test
🧩 관련 이슈
🧪 테스트 방법
변경 내용을 확인할 수 있는 방법을 단계별로 설명해 주세요.
코드 블록(```)으로 콘솔 로그나 테스트 코드 결과를 첨부해도 좋습니다.
예시: